#include <stdint.h>
#include "jimgutil.h"
#include "sample_log.h"
#include "rotate.h"

using namespace gdface;

JIMGUTIL_DLL_DECL int jimgutil_RGBA2BGR
(const uint8_t* input,int input_len, int width, int height, int stride, uint8_t* output, int output_len){
	if (input == nullptr || output == nullptr) {
		return JIMGUTIL_NULL_POINTER;
	}
	if (stride < width*4 || input_len != stride * height) {
		return JIMGUTIL_INVALID_INPUT_SIZE;
	}
	if (output_len != width * height * 3) {
		return JIMGUTIL_INVALID_OUTPUT_SIZE;
	}
	auto input_line_ptr = input;
	auto output_line_ptr = output;
	for (int y = 0; y < height; ++y) {
		for (int x = 0, p = 0; x < width * 4; x += 4, p += 3) {
			output_line_ptr[ p     ] = input_line_ptr[x + 2 ] ; // BLUE
			output_line_ptr[ p + 1 ] = input_line_ptr[x + 1 ] ; // GREEN
			output_line_ptr[ p + 2 ] = input_line_ptr[x     ] ; // RED
		}
		input_line_ptr += stride;
		output_line_ptr += width * 3;
	}
	return JIMGUTIL_OK;
}
JIMGUTIL_DLL_DECL int jimgutil_RGBA2RGB
(const uint8_t* input, int input_len, int width, int height, int stride, uint8_t* output, int output_len) {
	if (input == nullptr || output == nullptr) {
		return JIMGUTIL_NULL_POINTER;
	}
	if (stride < width * 4 || input_len != stride * height) {
		return JIMGUTIL_INVALID_INPUT_SIZE;
	}
	if (output_len != width * height * 3) {
		return JIMGUTIL_INVALID_OUTPUT_SIZE;
	}
	auto input_line_ptr = input;
	auto output_line_ptr = output;
	for (int y = 0; y < height; ++y) {
		for (int x = 0, p = 0; x < width * 4; x += 4, p += 3) {
			output_line_ptr[p    ] = input_line_ptr[x    ]; // RED
			output_line_ptr[p + 1] = input_line_ptr[x + 1]; // GREEN
			output_line_ptr[p + 2] = input_line_ptr[x + 2]; // BLUE
		}
		input_line_ptr += stride;
		output_line_ptr += width * 3;
	}
	return JIMGUTIL_OK;
}
JIMGUTIL_DLL_DECL int jimgutil_RGBA2GRAY
(const uint8_t* input, int input_len, int width, int height, int stride, uint8_t* output, int output_len) {
	if (input == nullptr || output == nullptr) {
		return JIMGUTIL_NULL_POINTER;
	}
	if (stride < width * 4 || input_len != stride * height) {
		return JIMGUTIL_INVALID_INPUT_SIZE;
	}
	if (output_len != width * height) {
		return JIMGUTIL_INVALID_OUTPUT_SIZE;
	}
	auto input_line_ptr = input;
	auto output_line_ptr = output;
	for (int y = 0; y < height; ++y) {
		for (int x = 0,p=0; x < width; ++x, p += 4) {
			uint32_t R = input_line_ptr[p     ];  // RED
			uint32_t G = input_line_ptr[p + 1 ];  // GREEN
			uint32_t B = input_line_ptr[p + 2 ];  // BLUE
			output_line_ptr[x] = (uint8_t)((R * 76 + G * 150 + B * 30) >> 8);
		}
		input_line_ptr += stride;
		output_line_ptr += width;
	}
	return JIMGUTIL_OK;
}
JIMGUTIL_DLL_DECL int jimgutil_RGB2BGR
(const uint8_t* input, int input_len, int width, int height, int stride, uint8_t* output, int output_len) {
	if (input == nullptr || output == nullptr) {
		return JIMGUTIL_NULL_POINTER;
	}
	if (stride < width*3 || input_len != stride * height) {
		return JIMGUTIL_INVALID_INPUT_SIZE;
	}
	if (output_len != width * height * 3) {
		return JIMGUTIL_INVALID_OUTPUT_SIZE;
	}
	auto input_line_ptr = input;
	auto output_line_ptr = output;

	for (int y = 0; y < height; ++y) {
		for (int x = 0; x < width * 3; x += 3) {
			output_line_ptr[x    ] = input_line_ptr[x + 2 ] ; // BLUE
			output_line_ptr[x + 1] = input_line_ptr[x + 1 ] ; // GREEN
			output_line_ptr[x + 2] = input_line_ptr[x     ] ; // RED
		}
		input_line_ptr  += stride;
		output_line_ptr += width * 3;
	}
	return JIMGUTIL_OK;
}
JIMGUTIL_DLL_DECL int jimgutil_swap3byte
(uint8_t* input, int input_len, int width, int height, int stride) {
	if (input == nullptr) {
		return JIMGUTIL_NULL_POINTER;
	}
	if (stride < width * 3 || input_len != stride * height) {
		return JIMGUTIL_INVALID_INPUT_SIZE;
	}
	auto input_line_ptr = input;

	for (int y = 0; y < height; ++y) {
		for (int x = 0; x < width * 3; x += 3) {
			std::swap(input_line_ptr[x], input_line_ptr[x + 2]);
		}
		input_line_ptr += stride;
	}
	return JIMGUTIL_OK;
}

JIMGUTIL_DLL_DECL int jimgutil_RGB2GRAY
(const uint8_t* input, int input_len, int width, int height, int stride, uint8_t* output, int output_len) {
	if (input == nullptr || output == nullptr) {
		return JIMGUTIL_NULL_POINTER;
	}
	if (stride < width * 3 || input_len != stride * height) {
		return JIMGUTIL_INVALID_INPUT_SIZE;
	}
	if (output_len != width * height ) {
		return JIMGUTIL_INVALID_OUTPUT_SIZE;
	}
	auto input_line_ptr = input;
	auto output_line_ptr = output;

	for (int y = 0; y < height; ++y) {
		for (int x = 0, p = 0; x < width; ++x, p += 3) {
			uint32_t R = input_line_ptr[p    ];  // RED
			uint32_t G = input_line_ptr[p + 1];  // GREEN
			uint32_t B = input_line_ptr[p + 2];  // BLUE
			output_line_ptr[x] = (uint8_t)((R * 76 + G * 150 + B * 30) >> 8);
		}
		input_line_ptr  += stride;
		output_line_ptr += width;
	}
	return JIMGUTIL_OK;
}

JIMGUTIL_DLL_DECL int jimgutil_BGR2RGBA
(const uint8_t* input, int input_len, int width, int height, int stride, uint8_t* output, int output_len) {
	if (input == nullptr || output == nullptr) {
		return JIMGUTIL_NULL_POINTER;
	}
	if (stride < width * 3 || input_len != stride * height) {
		return JIMGUTIL_INVALID_INPUT_SIZE;
	}
	if (output_len != width * height * 4) {
		return JIMGUTIL_INVALID_OUTPUT_SIZE;
	}
	auto input_line_ptr = input;
	auto output_line_ptr = output;

	for (int y = 0; y < height; ++y) {
		for (int x = 0, p = 0; x < width * 3; x += 3, p += 4) {
			output_line_ptr[p    ] = input_line_ptr[x + 2]; // RED
			output_line_ptr[p + 1] = input_line_ptr[x + 1]; // GREEN
			output_line_ptr[p + 2] = input_line_ptr[x    ]; // BLUE
			output_line_ptr[p + 3] = 0;
		}
		input_line_ptr  += stride;
		output_line_ptr += width * 4;
	}
	return JIMGUTIL_OK;
}
JIMGUTIL_DLL_DECL int jimgutil_RGB2RGBA
(const uint8_t* input, int input_len, int width, int height, int stride, uint8_t* output, int output_len) {
	if (input == nullptr || output == nullptr) {
		return JIMGUTIL_NULL_POINTER;
	}
	if (stride < width * 3 || input_len != stride * height) {
		return JIMGUTIL_INVALID_INPUT_SIZE;
	}
	if (output_len != width * height * 4) {
		return JIMGUTIL_INVALID_OUTPUT_SIZE;
	}
	auto input_line_ptr = input;
	auto output_line_ptr = output;

	for (int y = 0; y < height; ++y) {
		for (int x = 0, p = 0; x < width * 3; x += 3, p += 4) {
			output_line_ptr[p    ] = input_line_ptr[x    ]; // BLUE
			output_line_ptr[p + 1] = input_line_ptr[x + 1]; // GREEN
			output_line_ptr[p + 2] = input_line_ptr[x + 2]; // RED
			output_line_ptr[p + 3] = 0;
		}
		input_line_ptr  += stride;
		output_line_ptr += width * 4;
	}
	return JIMGUTIL_OK;
}


JIMGUTIL_DLL_DECL int jimgutil_rotate
(uint8_t* input, int input_len, int width, int height, int bpp, int angle) {
	if (input == nullptr ) {
		return JIMGUTIL_NULL_POINTER;
	}
	if (input_len != width * height * bpp) {
		return JIMGUTIL_INVALID_INPUT_SIZE;
	}
	if (bpp < 1 || bpp > 4) {
		return JIMGUTIL_INVALID_BPP;
	}
	if (0 == angle) {
		return JIMGUTIL_OK;
	}
	if (90 != angle && 180 != angle && 270 != angle) {
		return JIMGUTIL_INVALID_ANGLE;
	}
	return jimgutil::rotate(input, width, height, bpp, angle);
}
JIMGUTIL_DLL_DECL int jimgutil_hflip
(uint8_t* input, int input_len, int width, int height, int bpp) {
	if (input == nullptr) {
		return JIMGUTIL_NULL_POINTER;
	}
	if (input_len != width * height * bpp) {
		return JIMGUTIL_INVALID_INPUT_SIZE;
	}
	if (bpp < 1 || bpp > 4) {
		return JIMGUTIL_INVALID_BPP;
	}
	return jimgutil::hflip(input, width, height, bpp);
}
JIMGUTIL_DLL_DECL int jimgutil_vflip
(uint8_t* input, int input_len, int width, int height, int bpp) {
	if (input == nullptr) {
		return JIMGUTIL_NULL_POINTER;
	}
	if (input_len != width * height * bpp) {
		return JIMGUTIL_INVALID_INPUT_SIZE;
	}
	if (bpp < 1 || bpp > 4) {
		return JIMGUTIL_INVALID_BPP;
	}
	return jimgutil::vflip(input, width, height, bpp);
}
enum MatrixType { RGB, BGR, RGBA };
static int nv21To(const uint8_t *input, int width, int height, uint8_t *output,MatrixType type)
{
	int nv_off = width * height;
	int  i, j, y_index = 0;
	int y, u, v;
	int r, g, b, nv_index = 0;
	for (i = 0; i < height; i++) {
		for (j = 0; j < width; j++,++y_index) {
			nv_index = i / 2 * width + j - j % 2;

			y = input[y_index];
			u = input[nv_off + nv_index];
			v = input[nv_off + nv_index + 1];
			// yuv to rgb
			r = y + ((351 * (v - 128)) >> 8);  //r
			g = y - ((179 * (v - 128) + 86 * (u - 128)) >> 8); //g
			b = y + ((443 * (u - 128)) >> 8); //b

			r = ((r>255) ? 255 : (r<0) ? 0 : r);
			g = ((g>255) ? 255 : (g<0) ? 0 : g);
			b = ((b>255) ? 255 : (b<0) ? 0 : b);

			switch (type) {
			case RGB:
				output[y_index * 3 + 0] = (uint8_t)b;
				output[y_index * 3 + 1] = (uint8_t)g;
				output[y_index * 3 + 2] = (uint8_t)r;
				break;
			case BGR:
				output[y_index * 3 + 0] = (uint8_t)r;
				output[y_index * 3 + 1] = (uint8_t)g;
				output[y_index * 3 + 2] = (uint8_t)b;
				break;
			case RGBA:
				output[y_index * 4 + 0] = (uint8_t)b;
				output[y_index * 4 + 1] = (uint8_t)g;
				output[y_index * 4 + 2] = (uint8_t)r;
				output[y_index * 4 + 3] = 0;
				break;
			default:
				return JIMGUTIL_INVALID_MATRIX_TYPE;
			}

		}
	}
	return JIMGUTIL_OK;
}
static int nv21From(const uint8_t *input, int width, int height, uint8_t *output, MatrixType type)
{
	int nv_off = width * height;
	int i, j, y_index = 0;
	int y, u, v;
	int r, g, b, nv_index = 0;
	for (i = 0; i < height; i++) {
		for (j = 0; j < width; j++, ++y_index) {
			nv_index = i / 2 * width + j - j % 2;
			switch (type) {
			case RGB:
				b = input[y_index * 3 + 0];
				g = input[y_index * 3 + 1];
				r = input[y_index * 3 + 2];
				break;
			case BGR:
				r = input[y_index * 3 + 0];
				g = input[y_index * 3 + 1];
				b = input[y_index * 3 + 2];
				break;
			case RGBA:
				b = input[y_index * 4 + 0];
				g = input[y_index * 4 + 1];
				r = input[y_index * 4 + 2];
				break;
			default:
				return JIMGUTIL_INVALID_MATRIX_TYPE;
			}
			// rgb to yuv
			y = (77 * r + 150 * g + 29 * b) >> 8;
			u = ((-44 * r - 87 * g + 131 * b) >> 8) + 128;
			v = ((131 * r - 110 * g - 21 * b) >> 8) + 128;

			output[y_index] = (uint8_t)y;
			output[nv_off + nv_index] = (uint8_t)u;
			output[nv_off + nv_index + 1] = (uint8_t)v;
		}
	}
	return JIMGUTIL_OK;
}
JIMGUTIL_DLL_DECL int jimgutil_NV212RGB
(const uint8_t* input, int input_len, int width, int height, uint8_t* output, int output_len) {
	if (input == nullptr || output == nullptr) {
		return JIMGUTIL_NULL_POINTER;
	}
	if (input_len != width * height * 3 / 2) {
		return JIMGUTIL_INVALID_INPUT_SIZE;
	}
	if (output_len != width * height * 3) {
		return JIMGUTIL_INVALID_OUTPUT_SIZE;
	}
	return nv21To(input, width, height, output, RGB);
}
JIMGUTIL_DLL_DECL int jimgutil_NV212BGR
(const uint8_t* input, int input_len, int width, int height, uint8_t* output, int output_len) {
	if (input == nullptr || output == nullptr) {
		return JIMGUTIL_NULL_POINTER;
	}
	if (input_len != width * height * 3 / 2) {
		return JIMGUTIL_INVALID_INPUT_SIZE;
	}
	if (output_len != width * height * 3) {
		return JIMGUTIL_INVALID_INPUT_SIZE;
	}
	return nv21To(input, width, height, output, BGR);
}
JIMGUTIL_DLL_DECL int jimgutil_NV212RGBA
(const uint8_t* input, int input_len, int width, int height, uint8_t* output, int output_len) {
	if (input == nullptr || output == nullptr) {
		return JIMGUTIL_NULL_POINTER;
	}
	if (input_len != width * height * 3 / 2) {
		return JIMGUTIL_INVALID_INPUT_SIZE;
	}
	if (output_len != width * height * 4) {
		return JIMGUTIL_INVALID_OUTPUT_SIZE;
	}
	return nv21To(input, width, height, output, RGBA);
}
JIMGUTIL_DLL_DECL int jimgutil_RGB2NV21
(const uint8_t* input, int input_len, int width, int height, uint8_t* output, int output_len) {
	if (input == nullptr || output == nullptr) {
		return JIMGUTIL_NULL_POINTER;
	}
	if (input_len != width * height * 3) {
		return JIMGUTIL_INVALID_INPUT_SIZE;
	}
	if (output_len != width * height * 3 / 2) {
		return JIMGUTIL_INVALID_INPUT_SIZE;
	}
	if ((width & 1) != 0 || (height & 1) != 0) {
		return JIMGUTIL_REQUIRED_EVEN_WH;
	}
	return nv21From(input, width, height, output, RGB);
}
JIMGUTIL_DLL_DECL int jimgutil_BGR2NV21
(const uint8_t* input, int input_len, int width, int height, uint8_t* output, int output_len) {
	if (input == nullptr || output == nullptr) {
		return JIMGUTIL_NULL_POINTER;
	}
	if (input_len != width * height * 3) {
		return JIMGUTIL_INVALID_INPUT_SIZE;
	}
	if (output_len != width * height * 3 / 2) {
		return JIMGUTIL_INVALID_OUTPUT_SIZE;
	}
	if ((width & 1) != 0 || (height & 1) != 0) {
		return JIMGUTIL_REQUIRED_EVEN_WH;
	}
	return nv21From(input, width, height, output, BGR);
}
JIMGUTIL_DLL_DECL int jimgutil_RGBA2NV21
(const uint8_t* input, int input_len, int width, int height, uint8_t* output, int output_len) {
	if (input == nullptr || output == nullptr) {
		return JIMGUTIL_NULL_POINTER;
	}
	if (input_len != width * height * 4) {
		return JIMGUTIL_INVALID_INPUT_SIZE;
	}
	if (output_len != width * height * 3 / 2) {
		return JIMGUTIL_INVALID_OUTPUT_SIZE;
	}
	if ((width & 1) != 0 || (height & 1) != 0) {
		return JIMGUTIL_REQUIRED_EVEN_WH;
	}
	return nv21From(input, width, height, output, RGBA);
}
JIMGUTIL_DLL_DECL int jimgutil_nv21Rotate
(uint8_t* input, int input_len, int width, int height, int angle) {
	if (input == nullptr) {
		return JIMGUTIL_NULL_POINTER;
	}
	if (input_len != width * height * 3 /2) {
		return JIMGUTIL_INVALID_INPUT_SIZE;
	}

	if (0 == angle) {
		return JIMGUTIL_OK;
	}
	if (90 != angle && 180 != angle && 270 != angle) {
		return JIMGUTIL_INVALID_ANGLE;
	}
	// Rotate the Y luma		
	auto code = jimgutil::rotate(input,                  width,   height,   1, angle);
	if (code == JIMGUTIL_OK) {
		// Rotate the U and V color components
		code = jimgutil::rotate(input + width * height, width / 2, height / 2, 2, angle);
	}
	return code;
}
JIMGUTIL_DLL_DECL int jimgutil_nv21Hflip
(uint8_t* input, int input_len, int width, int height) {
	if (input == nullptr) {
		return JIMGUTIL_NULL_POINTER;
	}
	if (input_len != width * height * 3 / 2) {
		return JIMGUTIL_INVALID_INPUT_SIZE;
	}

	// flip the Y luma
	auto code = jimgutil::hflip(input, width, height, 1);
	if (code == JIMGUTIL_OK) {
		// flip the U and V color components
		code = jimgutil::hflip(input + width * height, width / 2, height / 2, 2);
	}
	return code;
}
JIMGUTIL_DLL_DECL int jimgutil_nv21Vflip
(uint8_t* input, int input_len, int width, int height) {
	if (input == nullptr) {
		return JIMGUTIL_NULL_POINTER;
	}
	if (input_len != width * height * 3 / 2) {
		return JIMGUTIL_INVALID_INPUT_SIZE;
	}
	// flip the Y luma
	auto code = jimgutil::vflip(input, width, height, 1);
	// flip the U and V color components
	if(code == JIMGUTIL_OK){
		code = jimgutil::vflip(input + width * height, width / 2, height / 2, 2);
	}
	return code;
}